// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package trace

import (
	"context"
	"fmt"
	"sync/atomic"
	_ "unsafe"
)

type traceContextKey struct{}

// NewTask创建类型为taskType的任务实例，并返回
// it以及承载该任务的上下文。
// 如果输入上下文包含任务，则新任务是其子任务。
// 
// taskType用于对任务实例进行分类。分析工具
// 像Go execution tracer一样，可能会假定系统中只有有限的
// 唯一任务类型的数量。
// 
// 返回的end函数用于标记任务的结束。
// 跟踪工具将任务延迟测量为任务创建
// 和调用end函数之间的时间，并提供每个任务类型的延迟
// 分布。
// 如果多次调用end函数，则在延迟测量中只使用第一个
// 调用。
// 
// ctx，任务：=跟踪。NewTask（ctx，awesomeTask）
// 跟踪。WithRegion（ctx，“preparation”，prepWork）
// /任务的准备
// /go func（）{
// /延迟任务。End（）
// /跟踪。WithRegion（ctx，“remainingWork”，remainingWork）
func NewTask(pctx context.Context, taskType string) (ctx context.Context, task *Task) {
	pid := fromContext(pctx).id
	id := newID()
	userTaskCreate(id, pid, taskType)
	s := &Task{id: id}
	return context.WithValue(pctx, traceContextKey{}, s), s

	// /我们分配一个新任务，甚至还有End函数当
	// 由于上下文和detach 
	// 函数可以跨跟踪启用/禁用边界使用而禁用跟踪时，
	// 这会使问题复杂化。ABCFDG 
	// /-跟踪。WithRegion被调用，因此创建了一个新的上下文ctx 
	// 和一个新的区域。
	// /-已禁用跟踪。
	// /-再次启用跟踪。
	// /-调用带有ctx的跟踪API。任务
	// 中的ID是否有效？
	// 
	// TODO（hyangah）：至少在禁用
	// 跟踪时减少开销。也许该id可以嵌入一个追踪
	// 轮号，并忽略之前
	// 追踪轮生成的id。
}

func fromContext(ctx context.Context) *Task {
	if s, ok := ctx.Value(traceContextKey{}).(*Task); ok {
		return s
	}
	return &bgTask
}

// 任务是用于跟踪用户定义的逻辑操作的数据类型。
type Task struct {
	id uint64
	// TODO（hyangah）：记录家长id？
}

// 结束标志着任务所代表的操作的结束。
func (t *Task) End() {
	userTaskEnd(t.id)
}

var lastTaskID uint64 = 0 // 上次发布的任务id 

func newID() uint64 {
	// TODO（hyangah）：使用per-P缓存
	return atomic.AddUint64(&lastTaskID, 1)
}

var bgTask = Task{id: uint64(0)}

// 日志使用给定的类别和消息发出一次性事件。
// 类别可以为空，API假设系统中只有少数
// 唯一类别。
func Log(ctx context.Context, category, message string) {
	id := fromContext(ctx).id
	userLog(id, category, message)
}

// Logf类似于Log，但该值是使用指定的格式规范格式化的。
func Logf(ctx context.Context, category, format string, args ...any) {
	if IsEnabled() {
		// 理想情况下，这应该只是Log，但这将在堆栈跟踪中再添加一个帧。
		id := fromContext(ctx).id
		userLog(id, category, fmt.Sprintf(format, args...))
	}
}

const (
	regionStartCode = uint64(0)
	regionEndCode   = uint64(1)
)

// WithRegion启动与其调用goroutine关联的区域，运行fn、
// 然后结束该区域。如果上下文包含任务，则该区域为
// 与该任务关联。否则，该区域将附加到背景
// 任务。
// 
// regionType用于对区域进行分类，因此应该只有一个
// 少数唯一的区域类型。
func WithRegion(ctx context.Context, regionType string, fn func()) {
	// 注意：
	// WithRegion有助于避免滥用API，但在实践中，
	// 这是非常严格的：
	// -使用WithRegion使从
	// 区域开始和结束捕获的堆栈跟踪相同。
	// -重构现有代码以与Region一起使用有时很难，并且会降低代码的可读性。
	// 例如，代码块嵌套在循环的深处，包含各种
	// 返回值的退出点
	// -重构代码以使用带有闭包的API可能会导致不同的GC行为，例如保留一些参数
	// 更长时间。
	// 这会导致代码中出现比我希望的更多的混乱，有时
	// 会降低代码的可读性。

	id := fromContext(ctx).id
	userRegion(id, regionStartCode, regionType)
	defer userRegion(id, regionEndCode, regionType)
	fn()
}

// StartRegion启动一个区域并返回一个函数，用于标记该区域的结束。返回区域的End函数必须从该区域启动的同一个goroutine调用
// 。
// 在每个goroutine中，区域必须嵌套。也就是说，区域开始
// 在该区域结束之后，必须在该区域结束之前结束。
// 建议使用
// 
// 延迟跟踪。StartRegion（ctx，“MyTraceRegion”）。End（）
// 
func StartRegion(ctx context.Context, regionType string) *Region {
	if !IsEnabled() {
		return noopRegion
	}
	id := fromContext(ctx).id
	userRegion(id, regionStartCode, regionType)
	return &Region{id, regionType}
}

// 区域是跟踪其执行时间间隔的代码区域。
type Region struct {
	id         uint64
	regionType string
}

var noopRegion = &Region{}

// 结束标记跟踪代码区域的结束。
func (r *Region) End() {
	if r == noopRegion {
		return
	}
	userRegion(r.id, regionEndCode, r.regionType)
}

// IsEnabled报告是否启用了跟踪。
// 此信息仅供参考。此函数返回时，跟踪状态
// 可能已更改。
func IsEnabled() bool {
	enabled := atomic.LoadInt32(&tracing.enabled)
	return enabled == 1
}

// 
// 函数体在运行时/跟踪中定义。转到
// 

// 发出UserTaskCreate事件。
func userTaskCreate(id, parentID uint64, taskType string)

// 发出UserTaskEnd事件。
func userTaskEnd(id uint64)

// 发出UserRegion事件。
func userRegion(id, mode uint64, regionType string)

// 发出用户日志事件。
func userLog(id uint64, category, message string)
